package com.ccys.common.util;



import lombok.extern.slf4j.Slf4j;

import org.springframework.stereotype.Component;

import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.*;



/**
 * PBKDF2Util 工具类
 */
@Slf4j
@Component
@SuppressWarnings("unchecked")
public class PBKDF2Util {
    /**
     * 哈希字节长度
     */
    private static final int HASH_BYTE_SIZE = 24;
    /**
     * 盐的字节长度
     */
    private static final int SALT_BYTE_SIZE = 24;
    /**
     * 密钥长度
     */
    private static final int HASH_SECRET_KEY_SIZE = 6*32;

    /**
     * @description 引入了 javax.crypto 包，以及 Base64 类来处理 Base64 编码。该示例使用了 PBKDF2WithHmacSHA256 算法，其中的 HASH_BYTE_SIZE 变量定义了期望的摘要字节大小。
     * 在方法内部，我们首先将盐值转换为字节数组，然后使用 PBEKeySpec 类来指定密码、盐值、迭代次数和摘要字节大小等参数。接着，我们实例化 SecretKeyFactory 对象，并调用其 generateSecret() 方法生成密钥对象。最后，我们将密钥对象的字节表示进行 Base64 编码，并返回编码后的字符串。
     * 您可以使用这个方法来生成 PBKDF2 算法的哈希值。请注意，为了安全起见，建议您在存储密码时同时保存盐值，并在验证密码时提供相同的盐值和迭代次数。
     * @param iterationValue  迭代次数  调用本类的generateIterationPassword 产生
     * @param password 明文密码
     * @param saltValue 盐（Base64 编码 ） 调用本类的generateSaltPassword 产生
     * @date     2024/1/8
     * @author   cloud fly
     */
    public static String createHashPassword(String password, Integer iterationValue, String saltValue) {
        try {
            //盐转字节数组
            byte[] salt = Base64.getDecoder().decode(saltValue);
            //System.out.println("盐的byte长度：" + salt.length);
            //使用 PBEKeySpec 类来指定密码、盐值、迭代次数和摘要字节大小等参数

            KeySpec spec = new PBEKeySpec( password.toCharArray(), salt, iterationValue, HASH_SECRET_KEY_SIZE);
            //实例化 SecretKeyFactory 对象 PBKDF2WithHmacSHA1,PBKDF2WithHmacSHA224,PBKDF2WithHmacSHA256,PBKDF2WithHmacSHA384,PBKDF2WithHmacSHA512
            SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
            //调用其 generateSecret() 方法生成密钥对象
            byte[] hash = factory.generateSecret(spec).getEncoded();
            //System.out.println("hash加密串的byte长度：" + salt.length);
            //将密钥对象的字节表示进行 Base64 编码
            String saltBase64 = Base64.getEncoder().encodeToString(salt);
            String iterationValueBase64 = PBKDF2Util.convertIntToBase64(iterationValue);
//            System.out.println("盐转Base64:" + saltBase64);
//            System.out.println("迭代次数值转Base64:" + iterationValueBase64);
            byte[] iterationValueBytes = PBKDF2Util.convertIntToByteLittleEndian(iterationValue);
            //System.out.println("迭代次数的byte长度：" + iterationValueBytes.length);
            byte[] valueToSave = new byte[salt.length + hash.length + iterationValueBytes.length];
            System.arraycopy(salt, 0, valueToSave, 0, salt.length);
            System.arraycopy(hash, 0, valueToSave, salt.length, hash.length);
            System.arraycopy(iterationValueBytes, 0, valueToSave, salt.length + hash.length, iterationValueBytes.length);
            return Base64.getEncoder().encodeToString(valueToSave);
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * @description 将整数按照小端字节序转换为字节数组后再转base64
     * @date     2024/1/9
     * @author   cloud fly
     */
    public static String convertIntToBase64(int number) {
        String base64StringBuffer = Base64.getEncoder().encodeToString(PBKDF2Util.convertIntToByteLittleEndian(number));
        return base64StringBuffer;
    }

    /**
     * @description int 类型转 小端字节序byte数组
     * @date     2024/1/9
     * @author   cloud fly
     */
    public static byte[] convertIntToByteLittleEndian(int number) {
        return ByteBuffer.allocate(4).order(java.nio.ByteOrder.LITTLE_ENDIAN).putInt(number).array();
    }


       /**
        * @description 生成迭代次数密码
        * @date     2024/1/9
        * @author   cloud fly
        */
    public static Integer generateIterationPassword() {
        Random random = new Random();
        //random.nextInt(10000, 11000)
        return random.nextInt(1000) + 10000;
    }

       /**
        * @description 生成随机盐
        * @date     2024/1/9
        * @author   cloud fly
        */
    public static String generateSaltPassword() {
        // 随机字符串长度，每个字符为 2 位
        int length = 24;
        // 包含可能出现的字符
        String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
        StringBuilder sb = new StringBuilder();
        SecureRandom secureRandom = new SecureRandom();
        for (int i = 0; i < length; i++) {
            int index = secureRandom.nextInt(chars.length());
            char randomChar = chars.charAt(index);
            sb.append(randomChar);
        }
        String randomString = sb.toString();
        byte[] inputBytes = randomString.getBytes(StandardCharsets.UTF_8);
        String base64String = Base64.getEncoder().encodeToString(inputBytes);
        //System.out.println("Random Base64 Encoding: " + base64String.length() + "长度" + base64String);
        return  base64String;
    }


    /**
     * @description 采用三方原始方式校验密码是否相等
     * @date     2024/1/8
     * @author   cloud fly
     */
    public static boolean verifyPassword(String password, Integer iterationValue, String hashedValue) {
        if(StringUtil.isEmpty(password) || StringUtil.isEmpty(hashedValue) || iterationValue == null) {
            return false;
        }
        try {
            //使用 Base64 解码哈希值
            byte[] hashBytes = Base64.getDecoder().decode(hashedValue);
            // 提取盐值
            byte[] salt = Arrays.copyOfRange(hashBytes, 0, SALT_BYTE_SIZE);
            //这个构造函数接受一个密码数组、一个盐值数组、一个迭代次数和一个密钥长度作为参数。使用这些参数来生成密钥。
            KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, iterationValue,HASH_SECRET_KEY_SIZE );

            SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
            byte[] hashPassword = factory.generateSecret(spec).getEncoded();
            // 提取盐值
            byte[] hashBytesSubPassword = Arrays.copyOfRange(hashBytes, SALT_BYTE_SIZE, SALT_BYTE_SIZE + HASH_BYTE_SIZE);
            // 原三方比较哈希值是否匹配
            return slowEquals(hashBytesSubPassword, hashPassword);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * @description 方法比较用户输入密码的哈希值和实际的哈希值是否相等，以防止时序攻击。
     * 如果它们匹配，函数返回 true，表示输入的密码是正确的；如果它们不匹配，函数返回 false。
     * @date     2024/1/8
     * @author   cloud fly
     */
    private static boolean slowEquals(byte[] a, byte[] b) {
        // 比较两个字节数组是否相等，防止时序攻击
        int diff = a.length ^ b.length;
        for (int i = 0; i < a.length && i < b.length; i++) {
            diff |= a[i] ^ b[i];
        }
        return diff == 0;
    }

    public static void main(String[] args) throws Exception {
        //String password = "2524536977@QQ.com";
        String password = "123456";
        Integer iterationPassword = 10813;
        byte[] bytes = ByteBuffer.allocate(4).order(java.nio.ByteOrder.LITTLE_ENDIAN).putInt(iterationPassword).array();
        String base64StringBuffer = Base64.getEncoder().encodeToString(bytes);
        System.out.println("base64StringBuffer:" + base64StringBuffer);
        System.out.println("数据库密钥长度=" + "zpGbnsQYHq6PmEqbq4zWInN77UCZ886TiVnAVdVRTlRrfVgRHL1+KcVP7vw2/YfsZicAAA==".length());
        System.out.println("生成出来密钥长度=" + "A7puhNBV/ywWxfDh5uumgXrrKxAP/YH9CGaTX5TVWx5VJGW0UU6BHSfI2SrbEcQGjefs2k3i".length());
        System.out.println("盐的长度=" + "zpGbnsQYHq6PmEqbq4zWInN77UCZ886T".length());

        String hash = PBKDF2Util.createHashPassword(password,iterationPassword,"zpGbnsQYHq6PmEqbq4zWInN77UCZ886T");
        System.out.println("哈希加密串:" + hash);
        System.out.println("哈希加密串长度:" + hash.length());
        if("zpGbnsQYHq6PmEqbq4zWInN77UCZ886TiVnAVdVRTlRrfVgRHL1+KcVP7vw2/YfsZicAAA==".equals(hash)) {
            System.out.println("和数据库密钥完全一致");
        }
        boolean b = PBKDF2Util.verifyPassword(password, iterationPassword, hash);
        System.out.println("密钥校验结果：" +b);
        //ByteBuffer.allocate()
        //System.out.println("随机盐长度" + PBKDF2Util.generateSaltPassword().length() );



    }




}
